package gorm

import (
	"time"

	mysqlDriver "github.com/go-sql-driver/mysql"
	"go-toolkit/library/log"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/plugin/dbresolver"
)

func NewTiDBOrm(c *Config) (db *DB) {
	if c.QueryTimeout == 0 || c.ExecTimeout == 0 || c.TranTimeout == 0 {
		panic("tidb must be set query/execute/transction timeout")
	}
	db, err := Open(c)
	if err != nil {
		log.Error("orm: open tidb error(%v)", err)
		panic(err)
	}
	return
}

func Open(c *Config) (db *DB, err error) {
	db = &DB{conf: c}
	cfg, err := mysqlDriver.ParseDSN(c.DSN)
	if err != nil {
		return
	}
	var dsns []string
	if cfg.Net == "discovery" {
		db.appid = cfg.Addr
		for _, addr := range db.disc() {
			dsns = append(dsns, genDSN(c.DSN, addr))
		}
	} else {
		dsns = append(dsns, c.DSN)
	}

	// 建立新连接
	db.Db, err = connect(db.conf, dsns)
	return
}

// 通过 dsns 建立连接并返回值
func connect(c *Config, dsns []string) (*gorm.DB, error) {
	var dialectors []gorm.Dialector

	for _, dsn := range dsns {
		dialectors = append(dialectors, mysql.Open(dsn))
	}

	// 建立 orm 连接并配置均衡负载连接
	db, err := gorm.Open(mysql.Open(dsns[0]), &gorm.Config{})
	err = db.Use(dbresolver.Register(dbresolver.Config{
		Sources: dialectors,
		Policy:  dbresolver.RandomPolicy{},
	}))

	if err != nil {
		log.Error("orm:connection error(%v)", err)
		panic(err)
	}

	// 暴露 DB 进行配置
	sqlDb, err := db.DB()
	sqlDb.SetMaxIdleConns(c.Idle)
	sqlDb.SetMaxOpenConns(c.Active)
	sqlDb.SetConnMaxLifetime(time.Duration(c.IdleTimeout))

	// 对 v1 的 orm 包进行兼容，但推荐显式标记
	if c.Backward {
		// 默认时间戳操作
		db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
		db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
	}
	return db, nil
}

// updateTimeStampForCreateCallback will set `ctime`, `mtime` when creating
func updateTimeStampForCreateCallback(db *gorm.DB) {
	now := time.Now()

	if createdAtField := db.Statement.Schema.LookUpField("ctime"); createdAtField != nil {
		createdAtField.Set(db.Statement.ReflectValue, now)
	}

	if updatedAtField := db.Statement.Schema.LookUpField("mtime"); updatedAtField != nil {
		updatedAtField.Set(db.Statement.ReflectValue, now)
	}
}

// updateTimeStampForCreateCallback will set `mtime` when updating
func updateTimeStampForUpdateCallback(db *gorm.DB) {
	now := time.Now()
	if updatedAtField := db.Statement.Schema.LookUpField("mtime"); updatedAtField != nil {
		updatedAtField.Set(db.Statement.ReflectValue, now)
	}
}
